﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Database.Core.Attributes;

namespace Database.Core
{
  internal class ObjectCache
  {
    private static ObjectCache _instance;

    internal static ObjectCache Instance => _instance ?? (_instance = new ObjectCache());

    private ConcurrentDictionary<Type, ConcurrentBag<KeyValuePair<PropertyInfo, Attribute[]>>> Cache { get; set; }

    private ObjectCache()
    {
      Cache = new ConcurrentDictionary<Type, ConcurrentBag<KeyValuePair<PropertyInfo, Attribute[]>>>();
    }

    internal bool ContainsProperty(Type type, string propertyName)
    {
      return Cache.ContainsKey(type) && Cache[type].Any(o => o.Key.Name.ToUpper() == propertyName.ToUpper());
    }

    internal bool ContainsColumnNameAttribute(Type type, string columnName)
    {
      return Cache.ContainsKey(type)
             && Cache[type].Any(o => o.Value.Any(a => a is ColumnName 
               && string.Equals(((ColumnName) a).Name, columnName, StringComparison.CurrentCultureIgnoreCase)));
    }

    internal bool ContainsEnumValueMap(PropertyInfo propertyInfo)
    {
      if (propertyInfo?.ReflectedType == null)
      {
        throw new ArgumentNullException(nameof(propertyInfo));
      }

      return Cache.ContainsKey(propertyInfo.ReflectedType) && Cache[propertyInfo.ReflectedType]
        .Any(o => o.Key.Name.ToUpper() == propertyInfo.Name.ToUpper() &&
                  o.Value.Any(a => a is EnumValueMap));
    }

    internal void AddCacheObject(Type type, PropertyInfo property, Attribute[] attributes)
    {
      if (!Cache.ContainsKey(type))
      {
        Cache.TryAdd(type, new ConcurrentBag<KeyValuePair<PropertyInfo, Attribute[]>>());
      }

      if (!ContainsProperty(type, property.Name))
      {
        Cache[type].Add(new KeyValuePair<PropertyInfo, Attribute[]>(property, attributes));
      }
    }

    internal List<PropertyInfo> RetrieveMatchingProperties(string name, Type type)
    {
      return Cache[type].Where(kvp => string.Equals(kvp.Key.Name, name, StringComparison.CurrentCultureIgnoreCase) ||
                                      kvp.Value.Any(a => a is ColumnName && ((ColumnName)a).Name.ToUpper() == name.ToUpper()))
        .Select(kvp => kvp.Key).ToList();
    }

    internal EnumValueMap[] RetrieveEnumValueMap(PropertyInfo propertyInfo)
    {
      if (propertyInfo?.ReflectedType == null)
      {
        throw new ArgumentNullException(nameof(propertyInfo));
      }

      return
        (EnumValueMap[]) Cache[propertyInfo.ReflectedType].FirstOrDefault(kvp => string.Equals(kvp.Key.Name, propertyInfo.Name, StringComparison.CurrentCultureIgnoreCase) &&
                                                                                 kvp.Value.Any(a => a is EnumValueMap))
          .Value;
    }
  }
}